Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.92% covered (success)
95.92%
94 / 98
66.67% covered (warning)
66.67%
4 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
Serializer
95.92% covered (success)
95.92%
94 / 98
66.67% covered (warning)
66.67%
4 / 6
27
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 create
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
1
 normalize
92.86% covered (success)
92.86%
26 / 28
0.00% covered (danger)
0.00%
0 / 1
12.05
 denormalizeOnMethodCall
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 denormalizeNewObject
93.33% covered (success)
93.33%
28 / 30
0.00% covered (danger)
0.00%
0 / 1
10.03
 denormalizeOnExistingObject
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2namespace Apie\Serializer;
3
4use Apie\Core\Context\ApieContext;
5use Apie\Core\Exceptions\InvalidTypeException;
6use Apie\Core\Lists\ItemHashmap;
7use Apie\Core\Lists\ItemList;
8use Apie\Core\Metadata\Concerns\UseContextKey;
9use Apie\Core\Metadata\MetadataFactory;
10use Apie\Core\ValueObjects\Utils;
11use Apie\Serializer\Context\ApieSerializerContext;
12use Apie\Serializer\Context\NormalizeChildGroup;
13use Apie\Serializer\Exceptions\ValidationException;
14use Apie\Serializer\Lists\NormalizerList;
15use Apie\Serializer\Normalizers\BooleanNormalizer;
16use Apie\Serializer\Normalizers\DateTimeNormalizer;
17use Apie\Serializer\Normalizers\DateTimeZoneNormalizer;
18use Apie\Serializer\Normalizers\EnumNormalizer;
19use Apie\Serializer\Normalizers\FloatNormalizer;
20use Apie\Serializer\Normalizers\IdentifierNormalizer;
21use Apie\Serializer\Normalizers\IntegerNormalizer;
22use Apie\Serializer\Normalizers\ItemListNormalizer;
23use Apie\Serializer\Normalizers\PaginatedResultNormalizer;
24use Apie\Serializer\Normalizers\PolymorphicObjectNormalizer;
25use Apie\Serializer\Normalizers\ReflectionTypeNormalizer;
26use Apie\Serializer\Normalizers\ResourceNormalizer;
27use Apie\Serializer\Normalizers\StringableCompositeValueObjectNormalizer;
28use Apie\Serializer\Normalizers\StringNormalizer;
29use Apie\Serializer\Normalizers\UploadedFileNormalizer;
30use Apie\Serializer\Normalizers\ValueObjectNormalizer;
31use Exception;
32use Psr\Http\Message\UploadedFileInterface;
33use ReflectionClass;
34use ReflectionMethod;
35
36class Serializer
37{
38    use UseContextKey;
39
40    public function __construct(private NormalizerList $normalizers)
41    {
42    }
43
44    public static function create(): self
45    {
46        return new self(new NormalizerList([
47            new PaginatedResultNormalizer(),
48            new UploadedFileNormalizer(),
49            new IdentifierNormalizer(),
50            new StringableCompositeValueObjectNormalizer(),
51            new PolymorphicObjectNormalizer(),
52            new DateTimeNormalizer(),
53            new DateTimeZoneNormalizer(),
54            new ResourceNormalizer(),
55            new EnumNormalizer(),
56            new ValueObjectNormalizer(),
57            new StringNormalizer(),
58            new IntegerNormalizer(),
59            new FloatNormalizer(),
60            new BooleanNormalizer(),
61            new ItemListNormalizer(),
62            new ReflectionTypeNormalizer(),
63        ]));
64    }
65
66    public function normalize(mixed $object, ApieContext $apieContext, bool $forceDefaultNormalization = false): string|int|float|bool|ItemList|ItemHashmap|null
67    {
68        $serializerContext = new ApieSerializerContext($this, $apieContext);
69        if (!$forceDefaultNormalization) {
70            foreach ($this->normalizers->iterateOverNormalizers() as $normalizer) {
71                if ($normalizer->supportsNormalization($object, $serializerContext)) {
72                    return $normalizer->normalize($object, $serializerContext);
73                }
74            }
75        }
76        if (is_array($object)) {
77            $count = 0;
78            $returnValue = [];
79            $isList = true;
80            foreach ($object as $key => $value) {
81                if ($key === $count) {
82                    $count++;
83                } else {
84                    $isList = false;
85                }
86                $returnValue[$key] = $serializerContext->normalizeChildElement($key, $value);
87            }
88            return $isList ? new ItemList($returnValue) : new ItemHashmap($returnValue);
89        }
90        if (!is_object($object)) {
91            if (in_array(get_debug_type($object), ['resource', 'resource (closed)'])) {
92                throw new InvalidTypeException($object, 'primitive');
93            }
94            return $object;
95        }
96        $metadata = MetadataFactory::getResultMetadata(new ReflectionClass($object), $apieContext);
97        $returnValue = [];
98
99        foreach ($metadata->getHashmap()->filterOnContext($apieContext, getters: true) as $fieldName => $metadata) {
100            if ($metadata->isField()) {
101                $returnValue[$fieldName] = $serializerContext->normalizeChildElement(
102                    $fieldName,
103                    $metadata->getValue($object, $apieContext)
104                );
105            }
106        }
107        return new ItemHashmap($returnValue);
108    }
109
110    public function denormalizeOnMethodCall(string|int|float|bool|ItemList|ItemHashmap|array|null|UploadedFileInterface $input, ?object $object, ReflectionMethod $method, ApieContext $apieContext): mixed
111    {
112        $serializerContext = new ApieSerializerContext($this, $apieContext);
113        try {
114            $arguments = $serializerContext->denormalizeFromMethod($input, $method);
115        } catch (Exception $error) {
116            throw ValidationException::createFromArray(['' => $error]);
117        }
118        return $method->invokeArgs($object, $arguments);
119    }
120
121    public function denormalizeNewObject(string|int|float|bool|ItemList|ItemHashmap|array|null|UploadedFileInterface $object, string $desiredType, ApieContext $apieContext): mixed
122    {
123        if (is_array($object)) {
124            $isList = false;
125            if ($desiredType === 'mixed') {
126                $isList = true;
127                $count = 0;
128                foreach (array_keys($object) as $key) {
129                    if ($key === $count) {
130                        $count++;
131                    } else {
132                        $isList = false;
133                        break;
134                    }
135                }
136            }
137            $object = $isList ? new ItemList($object) : new ItemHashmap($object);
138        }
139        if ($desiredType === 'mixed') {
140            return $object;
141        }
142        $serializerContext = new ApieSerializerContext($this, $apieContext);
143        foreach ($this->normalizers->iterateOverDenormalizers() as $denormalizer) {
144            if ($denormalizer->supportsDenormalization($object, $desiredType, $serializerContext)) {
145                return $denormalizer->denormalize($object, $desiredType, $serializerContext);
146            }
147        }
148        $refl = new ReflectionClass($desiredType);
149        if (!$refl->isInstantiable()) {
150            throw new InvalidTypeException($desiredType, 'a instantiable object');
151        }
152        $metadata = MetadataFactory::getCreationMetadata(
153            $refl,
154            $apieContext
155        );
156        $group = new NormalizeChildGroup(
157            $serializerContext,
158            $metadata
159        );
160        $normalizedData = $group->buildNormalizedData($refl, Utils::toArray($object));
161        return $normalizedData->createNewObject();
162    }
163
164    public function denormalizeOnExistingObject(ItemHashmap $object, object $existingObject, ApieContext $apieContext): mixed
165    {
166        $refl = new ReflectionClass($existingObject);
167        $metadata = MetadataFactory::getCreationMetadata(
168            $refl,
169            $apieContext
170        );
171        $serializerContext = new ApieSerializerContext($this, $apieContext);
172        $metadata = MetadataFactory::getModificationMetadata(
173            $refl,
174            $apieContext
175        );
176        $group = new NormalizeChildGroup(
177            $serializerContext,
178            $metadata
179        );
180        $normalizedData = $group->buildNormalizedData($refl, Utils::toArray($object));
181        return $normalizedData->modifyExistingObject($existingObject);
182    }
183}